# 中国区坏件盘点

import json
import os
import sys
import threading
import time
import tkinter as tk
from tkinter import ttk, messagebox, filedialog

import keyboard
import pyautogui
import polars as pl
import win32clipboard
import win32con
# import pandas as pd


class AutoClickerGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("自动点击器")
        self.root.geometry("800x600")

        self.stop_event = threading.Event()
        self.config = self.load_config()
        self.click_positions = []
        self.data_df = None  # 存储Excel数据

        self.create_widgets()
        self.load_values_to_gui()

    def create_widgets(self):
        # 创建主框架
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))

        # 点击序列编辑区域
        clicks_frame = ttk.LabelFrame(main_frame, text="点击位置配置", padding="5")
        clicks_frame.grid(row=0, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)

        # 创建树形视图来显示点击序列
        self.tree = ttk.Treeview(clicks_frame, columns=('名称', 'X坐标', 'Y坐标', '等待时间'), show='headings')
        self.tree.heading('名称', text='名称', anchor='center')
        self.tree.heading('X坐标', text='X坐标', anchor='center')
        self.tree.heading('Y坐标', text='Y坐标', anchor='center')
        self.tree.heading('等待时间', text='等待时间(ms)', anchor='center')

        # 设置每列的宽度和居中
        self.tree.column('名称', anchor='center', width=100)
        self.tree.column('X坐标', anchor='center', width=100)
        self.tree.column('Y坐标', anchor='center', width=100)
        self.tree.column('等待时间', anchor='center', width=100)

        self.tree.grid(row=0, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S))

        # 添加滚动条
        scrollbar = ttk.Scrollbar(clicks_frame, orient=tk.VERTICAL, command=self.tree.yview)
        scrollbar.grid(row=0, column=2, sticky=(tk.N, tk.S))
        self.tree.configure(yscrollcommand=scrollbar.set)

        # 编辑区域
        edit_frame = ttk.Frame(clicks_frame)
        edit_frame.grid(row=1, column=0, columnspan=2, pady=5)

        ttk.Label(edit_frame, text="名称:").grid(row=0, column=0)
        self.name_entry = ttk.Entry(edit_frame, width=10)
        self.name_entry.grid(row=0, column=1, padx=2)

        ttk.Label(edit_frame, text="X:").grid(row=0, column=2)
        self.x_entry = ttk.Entry(edit_frame, width=10)
        self.x_entry.grid(row=0, column=3, padx=2)

        ttk.Label(edit_frame, text="Y:").grid(row=0, column=4)
        self.y_entry = ttk.Entry(edit_frame, width=10)
        self.y_entry.grid(row=0, column=5, padx=2)

        ttk.Label(edit_frame, text="等待时间:").grid(row=0, column=6)
        self.wait_entry = ttk.Entry(edit_frame, width=10)
        self.wait_entry.grid(row=0, column=7, padx=2)

        # 编辑按钮
        btn_frame = ttk.Frame(clicks_frame)
        btn_frame.grid(row=2, column=0, columnspan=2, pady=5)

        ttk.Button(btn_frame, text="添加位置", command=self.add_position).pack(side=tk.LEFT, padx=2)
        ttk.Button(btn_frame, text="修改选中", command=self.edit_position).pack(side=tk.LEFT, padx=2)
        ttk.Button(btn_frame, text="删除选中", command=self.delete_position).pack(side=tk.LEFT, padx=2)
        ttk.Button(btn_frame, text="获取当前位置", command=self.get_cursor_position).pack(side=tk.LEFT, padx=2)
        ttk.Button(btn_frame, text="保存配置", command=self.save_config).pack(side=tk.LEFT, padx=2)

        # 控制按钮
        control_frame = ttk.Frame(main_frame)
        control_frame.grid(row=2, column=0, columnspan=2, pady=10)

        ttk.Button(control_frame, text="获取Excel数据", command=self.load_excel_data).pack(side=tk.LEFT, padx=5)
        self.start_button = ttk.Button(control_frame, text="开始", command=self.start_program)
        self.start_button.pack(side=tk.LEFT, padx=5)
        self.stop_button = ttk.Button(control_frame, text="停止", command=self.stop_program)
        self.stop_button.pack(side=tk.LEFT, padx=5)
        self.stop_button['state'] = 'disabled'

        # 数据状态标签
        self.data_status_label = ttk.Label(control_frame, text="未加载数据")
        self.data_status_label.pack(side=tk.LEFT, padx=5)

        # 在创建树形视图后，添加选择事件绑定
        self.tree.bind('<<TreeviewSelect>>', self.on_tree_select)

    def load_config(self):
        try:
            base_path = os.path.dirname(os.path.realpath(sys.argv[0]))
            config_path = os.path.join(base_path, 'config.json')
            with open(config_path, 'r', encoding='utf-8') as f:
                return json.load(f)
        except Exception as e:
            print(f"加载配置文件失败: {str(e)}")
            messagebox.showerror("错误", f"加载配置文件失败: {str(e)}")
            return None

    def load_values_to_gui(self):
        if self.config:
            # 加载点击序列
            for click in self.config['click_sequence']:
                pos = click['position']
                self.tree.insert('', 'end', values=(
                    click['name'],  # 使用配置中的名称而不是序号
                    pos[0], 
                    pos[1], 
                    click['wait_time']
                ))

    def add_position(self):
        try:
            name = self.name_entry.get()
            x = int(self.x_entry.get())
            y = int(self.y_entry.get())
            wait = int(self.wait_entry.get())

            # 插入新位置
            self.tree.insert('', 'end', values=(name, x, y, wait))

            # 清空输入框
            self.name_entry.delete(0, tk.END)
            self.x_entry.delete(0, tk.END)
            self.y_entry.delete(0, tk.END)
            self.wait_entry.delete(0, tk.END)

        except ValueError:
            messagebox.showerror("错误", "请输入有效的数字")

    def edit_position(self):
        selected = self.tree.selection()
        if not selected:
            messagebox.showwarning("警告", "请先选择一个点击位置")
            return

        try:
            x = int(self.x_entry.get())
            y = int(self.y_entry.get())
            wait = int(self.wait_entry.get())

            # 更新选中项
            item = selected[0]
            current_index = self.tree.item(item)['values'][0]
            self.tree.item(item, values=(current_index, x, y, wait))

        except ValueError:
            messagebox.showerror("错误", "请输入有效的数字")

    def delete_position(self):
        selected = self.tree.selection()
        if not selected:
            messagebox.showwarning("警告", "请先选择一个点击位置")
            return

        self.tree.delete(selected)
        # 重新编号
        for i, item in enumerate(self.tree.get_children(), 1):
            values = self.tree.item(item)['values']
            self.tree.item(item, values=(i,) + tuple(values[1:]))

    def get_cursor_position(self):
        self.root.iconify()  # 最小化窗口

        # 创建一个临时窗口来捕获鼠标事件
        temp_window = tk.Toplevel()
        temp_window.attributes('-alpha', 0.01)  # 设置几乎透明
        temp_window.attributes('-fullscreen', True)
        temp_window.attributes('-topmost', True)

        # 用于跟踪双击
        last_click_time = [0]

        def on_right_click(event):
            current_time = time.time()
            if current_time - last_click_time[0] < 0.3:  # 300ms内的双击
                pos = (event.x_root, event.y_root)
                self.x_entry.delete(0, tk.END)
                self.y_entry.delete(0, tk.END)
                self.x_entry.insert(0, str(pos[0]))
                self.y_entry.insert(0, str(pos[1]))
                temp_window.destroy()
                self.root.deiconify()  # 恢复主窗口
            last_click_time[0] = current_time

        temp_window.bind('<Button-3>', on_right_click)  # 绑定鼠标右键事件

        # 显示提示标签
        label = tk.Label(temp_window,
                         text="双击鼠标右键获取位置\nESC键取消",
                         font=('Arial', 24),
                         bg='white')
        label.pack(expand=True)

        def on_escape(event):
            temp_window.destroy()
            self.root.deiconify()

        temp_window.bind('<Escape>', on_escape)

    def save_config(self):
        try:
            config = {
                'click_sequence': []
            }

            for item in self.tree.get_children():
                values = self.tree.item(item)['values']
                config['click_sequence'].append({
                    'name': values[0],
                    'position': [int(values[1]), int(values[2])],
                    'wait_time': int(values[3])
                })

            base_path = os.path.dirname(os.path.realpath(sys.argv[0]))
            config_path = os.path.join(base_path, 'config.json')
            with open(config_path, 'w', encoding='utf-8') as f:
                json.dump(config, f, indent=4, ensure_ascii=False)

            messagebox.showinfo("成功", "配置已保存")

        except Exception as e:
            messagebox.showerror("错误", f"保存配置失败: {str(e)}")

    def load_excel_data(self):
        try:
            file_path = filedialog.askopenfilename(
                title="选择Excel文件",
                filetypes=[("Excel files", "*.xlsx *.xlsm *.xls")]
            )
            
            if file_path:
                self.data_df = pl.read_excel(
                    file_path,
                    read_options={'use_columns': ['货位', '条码']},
                    schema_overrides={"货位": pl.String, "条码": pl.String},
                    engine="calamine"
                )
                
                # 按货位分组并计算每个货位的条码数量，保持原始顺序
                summary = (self.data_df.group_by('货位', maintain_order=True)
                          .agg(pl.col('条码').count().alias('条码数量')))
                
                row_count = len(self.data_df)
                unique_locations = len(summary)
                self.data_status_label.config(
                    text=f"已加载 {unique_locations} 个货位，共 {row_count} 条数据"
                )
                messagebox.showinfo(
                    "成功", 
                    f"成功加载数据:\n货位数量: {unique_locations}\n总条码数量: {row_count}"
                )
        except Exception as e:
            messagebox.showerror("错误", f"加载Excel文件失败: {str(e)}")

    def start_program(self):
        if self.data_df is None:
            messagebox.showwarning("警告", "请先加载Excel数据")
            return

        try:
            # 获取最新配置
            self.config = {
                'click_sequence': []
            }

            for item in self.tree.get_children():
                values = self.tree.item(item)['values']
                self.config['click_sequence'].append({
                    'name': values[0],
                    'position': [int(values[1]), int(values[2])],
                    'wait_time': int(values[3])
                })

            required_positions = ['货位', '条码', '查看', '匹配', '确定1', '提交', '确定2']
            configured_positions = [click['name'] for click in self.config['click_sequence']]

            missing_positions = [pos for pos in required_positions if pos not in configured_positions]
            if missing_positions:
                messagebox.showwarning("警告", f"缺少以下位置的配置：{', '.join(missing_positions)}")
                return

            self.stop_event.clear()
            self.start_button['state'] = 'disabled'
            self.stop_button['state'] = 'normal'

            # 启动处理线程
            self.click_thread = threading.Thread(
                target=process_data,
                args=(self.stop_event, self.config, self.data_df),
                daemon=True
            )
            self.click_thread.start()

            # 启动键盘监听线程
            self.keyboard_thread = threading.Thread(
                target=self.listen_for_ctrl_z,
                daemon=True
            )
            self.keyboard_thread.start()

        except Exception as e:
            messagebox.showerror("错误", f"启动程序失败: {str(e)}")
            self.stop_program()

    def stop_program(self):
        self.stop_event.set()
        self.start_button['state'] = 'normal'
        self.stop_button['state'] = 'disabled'

    def listen_for_ctrl_z(self):
        while not self.stop_event.is_set():
            if keyboard.is_pressed('ctrl+z'):
                print("检测到Ctrl+Z，程序正在退出...")
                self.stop_program()
                break
            time.sleep(0.1)

    def on_tree_select(self, event):
        """当在树形视图中选择一行时触发"""
        selected = self.tree.selection()
        if selected:  # 如果有选中项
            # 获取选中行的值
            values = self.tree.item(selected[0])['values']
            
            # 清空输入框
            self.name_entry.delete(0, tk.END)
            self.x_entry.delete(0, tk.END)
            self.y_entry.delete(0, tk.END)
            self.wait_entry.delete(0, tk.END)
            
            # 填入选中行的值
            self.name_entry.insert(0, values[0])  # 名称
            self.x_entry.insert(0, values[1])     # X坐标
            self.y_entry.insert(0, values[2])     # Y坐标
            self.wait_entry.insert(0, values[3])  # 等待时间


def set_clipboard(text):
    """设置剪贴板内容"""
    win32clipboard.OpenClipboard()
    win32clipboard.EmptyClipboard()
    win32clipboard.SetClipboardText(str(text), win32con.CF_UNICODETEXT)
    win32clipboard.CloseClipboard()

def process_data(stop_event, config, data_df):
    try:
        print("程序将在3秒后开始执行...")
        time.sleep(3)

        # 按货位分组，保持原始顺序
        locations = (data_df.group_by('货位', maintain_order=True)
                    .agg([pl.col('条码').alias('条码列表')]))

        # 获取各个位置的配置
        pos_config = {click['name']: click for click in config['click_sequence']}

        # 遍历每个货位
        for row in locations.iter_rows(named=True):
            if stop_event.is_set():
                print("检测到停止信号，退出程序。")
                break

            location = row['货位']
            barcodes = row['条码列表']

            # 1. 处理货位
            pos = pos_config['货位']['position']
            pyautogui.moveTo(pos[0], pos[1], duration=0.5)
            pyautogui.click()
            time.sleep(0.5)
            
            # 使用keyboard模拟键盘输入
            keyboard.write(str(location), delay=0.05)
            time.sleep(0.2)  # 短暂等待
            keyboard.press_and_release('enter')
            time.sleep(ms_to_seconds(pos_config['货位']['wait_time']))

            # 2. 处理该货位的所有条码
            for barcode in barcodes:
                if stop_event.is_set():
                    break

                pos = pos_config['条码']['position']
                pyautogui.moveTo(pos[0], pos[1], duration=0.5)
                pyautogui.click()
                time.sleep(0.5)
                
                # 使用keyboard模拟键盘输入
                keyboard.write(str(barcode),delay=0.05)
                time.sleep(0.2)  # 短暂等待
                keyboard.press_and_release('enter')
                time.sleep(ms_to_seconds(pos_config['条码']['wait_time']))

            # 3. 依次点击其他按钮
            for action in ['查看', '匹配', '确定1', '提交', '确定2']:
                if stop_event.is_set():
                    break

                pos = pos_config[action]['position']
                pyautogui.moveTo(pos[0], pos[1], duration=0.5)
                pyautogui.click()
                time.sleep(ms_to_seconds(pos_config[action]['wait_time']))

            print(f"完成处理货位: {location}")

        if not stop_event.is_set():
            print("\n所有数据处理完成！")

    except pyautogui.FailSafeException:
        print("触发了故障保护，程序已停止")
    except Exception as e:
        print(f"发生错误: {str(e)}")


def ms_to_seconds(ms):
    return ms / 1000.0


if __name__ == "__main__":
    root = tk.Tk()
    app = AutoClickerGUI(root)
    root.mainloop()